<?php
// $Id: hotel_booking.util.inc,v 1.1.2.3 2010/07/19 09:34:25 larowlan Exp $
/*
 * @file hotel_booking.util.inc
 * Provides util functions for hotel_booking module
 * @copyright Copyright(c) 2010 Lee Rowlands
 * @license GPL v2 http://www.fsf.org/licensing/licenses/gpl.html
 * @author Lee Rowlands leerowlands at rowlands-bcs dot com
 *
 */

/**
 * Fetch array of base rates
 * @param $name_only boolean TRUE to fetch entire row, else false
 * @return array
*/
function hotel_booking_get_rates($name_only = FALSE) {
  $result = db_query("SELECT * FROM {hotel_booking_rates}");
  $rate_list = array();
  while ($row = db_fetch_object($result)) {
    if ($name_only) {
      $rate_list[$row->hbrid] = check_plain($row->name);
    }
    else {
      $rate_list[$row->hbrid] = $row;
    }
  }
  return $rate_list;
}

/**
 * Fetch array of rate modifiers
 * @param $names_only boolean TRUE to fetch name of modifier only
 * @return array of modifiers
*/
function hotel_booking_get_rate_modifiers($names_only = FALSE) {
  $result = db_query("SELECT * FROM {hotel_booking_rate_modifiers}
                     ORDER BY method DESC, rate ASC");
  $modifiers = array();
  if ($names_only) {
    //we add a 'base rate' option - ie no modifier
    $modifiers[0] = t('Base rate');
  }
  while ($row = db_fetch_object($result)) {
    if ($names_only) {
      switch ($row->method) {
        case 'P':
          $modifiers[$row->hbrmid] = t('Base rate plus !percent%', array('!percent' => number_format($row->rate, 2, '.', ',')));
          break;
        default:
          $modifiers[$row->hbrmid] = t('Base rate plus !rate', array(
            '!rate' => uc_price($row->rate, array('revision' => 'formatted'))));
      }
    }
    else {
      $modifiers[$row->hbrmid] = $row;
    }
  }
  return $modifiers;
}

/**
 * Fetch array of occupancy modifiers
 * @param $names_only boolean TRUE to fetch names only
 * @param $filter string A for adults, C for children, B for both
*/
function hotel_booking_get_occupancy_modifiers($names_only = FALSE, $filter = FALSE) {
  $query = "SELECT * FROM {hotel_booking_occupancy_modifiers}";
  if ($filter && !in_array($filter, array('A', 'B', 'C'))) {
    watchdog('hotel booking', 'Invalid filter passed for function hotel_booking_get_occupancy_modifiers, valid filters are A, B or C');
    return array();
  }
  if ($filter) {
    $query .= "
    WHERE type = '%s'";
    $params[] = $filter;
  }
  $query .= "
            ORDER BY type ASC, threshold ASC, rate ASC";
  $result = db_query($query, $params);
  $modifiers = array();
  while ($row = db_fetch_object($result)) {
    if ($names_only) {
      $modifiers[$row->hbomid] = t(
        '!rate each additional over !threshold',
        array(
          '!rate' => uc_price($row->rate, array('revision' => 'formatted')),
          '!threshold' => $row->threshold
        )
      );
    }
    else {
      $modifiers[$row->hbomid] = $row;
    }
  }
  return $modifiers;
}

/**
 * Check given date againt the current expiration date (according to settings). Returns true in case the given date is
 * yet to be expired, false otherwise
 * @param $date object date object
 */
function hotel_booking_non_zeroed_date($date) {
  $expiration_dt = hotel_booking_expiration_date();
  return (int)$date->format('U') > (int)$expiration_dt->format('U');
}

/**
 * Helper function to get the proper expiration date.
 * Using cron exripation process and cancellation process
 */
function hotel_booking_expiration_date() {
  $expiration = variable_get('hotel_booking_expiration', 'yesterday');
  $today = date_make_date('now');
  $adjustments = array(
    'yesterday' => '-1 days',
    'tomorrow' => '+1 days'
  );
  if ($adjustments[$expiration]) {
    $today->modify($adjustments[$expiration]);
  }
  return $today;
}

/**
 * Helper function to determine if there is a booking item in the cart.
 */
function hotel_booking_item_in_cart() {
  $items = uc_cart_get_contents();
  foreach ($items as $item) {
    if ($item->data['module'] == 'hotel_booking') {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Util to test if hotel_booking item in order
 * @param $order object std uc_order object
*/
function hotel_booking_item_in_order($order) {
  foreach ($order->products as $product) {
    if ($product->data['module'] == 'hotel_booking') {
      //this is a hotel booking product
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Utility function to fetch check in night and check out night
 * @param $nights array array of nights as per product->data['nights'] in cart and order products
 * @return array($check_in, $check_out)
 * @author larowlan leerowlands at rowlands-bcs.com
*/
function _hotel_booking_check_in_out($nights) {
  $check_in = date_make_date($nights[0], NULL, DATE_ISO);
  $last_night = date_make_date($nights[count($nights) - 1], NULL, DATE_ISO);
  $last_night->modify('+ 1 day'); //check out is day after last night
  return array($check_in, $last_night);
}

/**
 * Helper function to remove addon items from the cart.
 */
function hotel_booking_remove_upgrades($data = array()) {
  if ($data) {
    foreach ($data as $nid => $cart_item_id) {
      db_query('DELETE FROM {uc_cart_products} WHERE cart_item_id = %d', $cart_item_id);
    }
  }
}

/**
 * fetch default values for booking form from either
 * a)submitted values
 * b)user's last search (auth user)
 * c)user's last search (anon user - from session vars)
 * @param $form_state form state as submitted
 * @return array of form default values
*/
function hotel_booking_defaults($form_state) {
  global $user;
  //retrieve defaults
  if ($form_state['values']) { //form was submitted
    $defaults = array(
      'check_in' => $form_state['values']['check_in'],
      'nights'   => $form_state['values']['nights'],
      'adults'   => $form_state['values']['adults'],
      'children' => $form_state['values']['children'],
      'smoking'  => $form_state['values']['smoking']
    );
  }
  else { //has user submitted form before
    if ($user->uid && $user->hotel_booking_details) { //user is logged in and has defaults
      //get defaults
      $defaults = $user->hotel_booking_details;
      $date = date_make_date($defaults['check_in']);
      if (!$date || (int)$date->format('U') < time()) {
        $today = date_now();
        $defaults['check_in'] = date_format_date($today, variable_get('uc_date_format_default', 'd M Y')); //today's date
      }
    }
    elseif ($_SESSION['hotel_booking_defaults']) {
      $defaults = $_SESSION['hotel_booking_defaults']; //anonymous user but has submitted before
    }
    else { //just use defaults
      $today = date_now();
      $defaults = array(
        'date'     => date_format_date($today, variable_get('uc_date_format_default', 'd M Y')), //today's date
        'nights'   => 3, //optimistic!
        'adults'   => 2, //most stays would be two adults
        'children' => 0,
        'smoking'  => 3
      );
    }
  }
  return $defaults;
}

/**
 * function hotel_booking_save_defaults
 * saves search values submitted for user convenience
 *
 * @param $form_state the submitted form_state var
 * @return none
 */

function hotel_booking_save_defaults($form_state) {
  //get submitted vars into a nice array
  $defaults = array(
    'nights' => $form_state['values']['nights'],
    'adults' => $form_state['values']['adults'],
    'children' => $form_state['values']['children']
  );
  if (variable_get('hotel_booking_display_smoking', TRUE)) {
    $defaults['smoking'] = $form_state['values']['smoking'];
  }
  else {
    $defaults['smoking'] = 0;
  }
  $date = date_make_date($form_state['values']['check_in']);
  $defaults['check_in'] = date_format_date($date, variable_get('uc_date_format_default', 'd M Y')); //remove time

  global $user;
  //save search details for convenience
  if ($user->uid) { //user is logged in
    //set defaults
    $hotel_booking_details = array('hotel_booking_details' => $defaults);
    user_save($user, $hotel_booking_details);
  }
  else {
    $_SESSION['hotel_booking_defaults'] = $defaults;
  }
}

/**
 * Function returns query to return provide list of nodes available
 * for given date and parameters
 * @param $alias string alias for the node table to join against
 * @return string
 * @see hotel_booking_tnid_sql
*/
function hotel_booking_available_rooms_sql($alias = 'node', $field = 'tnid') {
  $sql = hotel_booking_tnid_sql($alias, $field) ." IN (SELECT nid FROM (
            SELECT nid,
            COUNT(*) AS nights
            FROM {hotel_booking_availability_calendars}
            WHERE calendar_dt >= '%s'
            AND calendar_dt < '%s' /*this has to be less than as they dont stay this night*/
            AND minimum_stay <= %d
            AND minimum_occupancy <= %d
            AND available > 0
            GROUP BY 1) AS nights
         WHERE nights.nights = %d
      )
      AND ". hotel_booking_tnid_sql($alias, $field) ." NOT IN (
        SELECT nid
        FROM {hotel_booking_availability_calendars}
        WHERE calendar_dt = '%s'
        AND no_check_in = 1
      )
      AND ". hotel_booking_tnid_sql($alias, $field) ." NOT IN (
        SELECT nid
        FROM {hotel_booking_availability_calendars}
        WHERE calendar_dt = '%s'
        AND no_check_out = 1
      )
      AND %d BETWEEN minimum_occupancy AND capacity";
  return $sql;
}

/**
 * Function returns array
 * @param $check_in string date/time
 * @param $check_out string date/time
 * @param $nights int number of nights
 * @param $adults int number of adults
 * @param $children int number of children
 * @return formatted array suitable for sql args
*/
function hotel_booking_available_rooms_sql_args($check_in, $check_out, $nights, $adults, $children) {
  return array($check_in, $check_out, $nights,
      ($adults + $children), $nights, $check_in, $check_out, ($adults + $children));
}

/**
 * This function provides a case statement
 * to query against tnid or nid of the node for multi-lingual sites
 * where availability is set against translation source but used by translations
 *
 * @param $alias string alias of the node table
 * @param $field string alias of the field table
*/
function hotel_booking_tnid_sql($alias, $field) {
  if ($field == 'nid') {
    return sprintf('%s.nid', $alias);
  }
  return sprintf("CASE WHEN %s.%s = 0 THEN %s.nid ELSE %s.%s END", $alias, $field,
                 $alias, $alias, $field);
}

/**
 * Returns array of node ids available for booking
 * @param $check_in string date/time
 * @param $check_out string date/time
 * @param $nights int number of nights
 * @param $adults int number of adults
 * @param $children int number of children
 * @param $smoking int value for smoking (3 = no preference)
 * @param $nid array array of node id - pass this to check specific rooms only
 * @return array of nids
 *
*/
function hotel_booking_get_available_rooms($check_in, $check_out, $nights, $adults, $children, $smoking = 3, $nids = array()) {
  $rooms = array();

  $sql = "SELECT hbrt.nid
          FROM {hotel_booking_room_types} hbrt
          INNER JOIN {node} n
          ON hbrt.vid = n.vid
          WHERE ". hotel_booking_available_rooms_sql('n'); //joining against node to get correct vid
  $params = hotel_booking_available_rooms_sql_args(
    $check_in,
    $check_out,
    $nights,
    $adults,
  $children);
  if ($smoking < 3) {
    $sql .= " AND smoking = %d";
    $params[] = $smoking;
  }
  if (count($nids)) {
    //check specific rooms
    $sql .= " AND ". hotel_booking_tnid_sql('n', 'tnid') ." IN (". db_placeholders($nids) .") ";
    $params = array_merge($params, $nids);
  }
  if (function_exists('i18n_get_lang')) {
    //language testing
    $sql .= " AND n.language IN ('%s', '%s')";
    $params[] = ''; //no language
    $params[] = i18n_get_lang(); //current language
  }
  $rooms_res = db_query($sql, $params);
  while ($room = db_fetch_array($rooms_res)) {
    $rooms[] = $room['nid'];
  }
  return $rooms;
}


/**
 * Gets array of room rate results
 * @param $check_in string date/time of check in
 * @param $check_out string date/time of check out
 * @param $rooms array of nids of available rooms
 * @param $adults int number of adults
 * @param $children int number of children
 * @param $smoking int smoking/non smoking flag
 * @return array of form array(results => array (nid1 => array('prices' => array(date1 => rate, date2 => rate)),
                                            nid2 => array('prices' => array(date1 => rate, date2 => rate))),
                                totals => array(nid1 => price, nid2 => price))
*/
function hotel_booking_calculate_prices($check_in, $check_out, $rooms,
                                         $adults, $children, $smoking) {
  if (!$rooms || count($rooms) == 0) {
    return array(
      'totals' => array(),
      'results' => array()
    );
  }
  $dates = $results = array();
  //cache caldates
  $start_date = date_make_date($check_in);
  $end_date = date_make_date($check_out);
  $query = "SELECT r.calendar_dt, r.rate, hbrt.nid
            FROM {hotel_booking_room_types} hbrt
            INNER JOIN {node} n
              ON hbrt.vid = n.vid
            INNER JOIN {hotel_booking_rate_calendars} r
              ON hbrt.hbrid = r.hbrid
            WHERE hbrt.nid IN (". db_placeholders($rooms) .")";
  $date_clauses = array();
  while ((int)$start_date->format('U') < (int)$end_date->format('U')) {
    /*for some reason some versions of MySQL don't like date fields
     and in clauses, have to do it like this*/
    $dates[] = $start_date->format(DATE_FORMAT_DATE);
    $date_clauses[] = " r.calendar_dt = '%s' ";
    $start_date->modify('+ 1 day');
  }

  //cache baserates - get them in one query not heaps
  if (count($dates) > 0) {
    $query .= " AND (". implode(' OR ', $date_clauses) .")";
  }
  $baserates_res = db_query($query, array_merge($rooms, $dates));
  while ($baserate = db_fetch_array($baserates_res)) {
    if (!isset($results[$baserate['nid']])) {
      //initialise
      $results[$baserate['nid']] = array('prices' => array());
    }
    $results[$baserate['nid']]['prices'][$baserate['calendar_dt']] = $baserate['rate'];
  }
  /*results is now a 2D array of form array (nid1 => array('prices' => array(date1 => rate, date2 => rate)),
                                            nid2 => array('prices' => array(date1 => rate, date2 => rate)))*/
  //rate modifiers
  foreach (module_implements('modify_room_rate') as $module) {
    $function = $module .'_modify_room_rate';
    if (function_exists($function)) {
      foreach ($results as $nid => $details) {
        $total = 0;
        $reset = TRUE;
        foreach ($details['prices'] as $date => $price) {
          $price = $function($price, node_load($nid), $date, $adults,
                              $children, $smoking, $check_in, $check_out, $reset);
          $results[$nid]['prices'][$date] = $price;
          $total += $price;
          $reset = FALSE; //can used static vars for this node, need new ones when node changes
        }
        $results[$nid]['total'] = $total;
        $sorted_rates[$nid] = $total;
      }
    }
  }
  return array(
    'results' => $results,
    'totals' => $sorted_rates
  );
}

/**
 * Provides utility to calculate end date
 * @param $check_in string date/time of check in
 * @param $nights int number of nights of stay
 * @param $last_night boolean, TRUE to return last night instead of check out date
 * @return string date/time of check out/last night
*/
function hotel_booking_calculate_checkout($check_in, $nights, $last_night = FALSE) {

  $last_dt = date_make_date($check_in);
  $last_dt->modify('+'. ($nights - 1) .' days');
  if ($last_night) {
    return $lastnight->format(DATE_FORMAT_DATETIME);
  }
  $last_dt->modify('+ 1 day');
  return $last_dt->format(DATE_FORMAT_DATETIME);
}

/**
 * Get from price for a room
 * looks at lowest base rate + modifiers
 * for future dates
 * @param $nid node id of room
 * @return mixed the price
*/
function hotel_booking_room_from_rate($nid) {
  $query = "SELECT MIN(hbrc.rate) AS rate, hbrm.method,
            CASE WHEN hbrm.rate IS NULL
              THEN 0
              ELSE hbrm.rate
            END AS modifier
            FROM {hotel_booking_room_types} hbrt
            INNER JOIN {hotel_booking_rate_calendars} hbrc
            ON hbrc.hbrid = hbrt.hbrid
            INNER JOIN {node} n
            ON n.vid = hbrt.vid
            LEFT JOIN {hotel_booking_rate_modifiers} hbrm
            ON hbrt.hbrmid = hbrm.hbrmid
            WHERE calendar_dt > '%s'
            AND hbrc.rate <> 0
            AND n.nid = %d
            GROUP BY 2, 3";
  $params = array(date('Y-m-d'), $nid);
  $details = db_fetch_array(db_query($query, $params));
  $rate = $details['rate'];
  if (!$rate) {
    return 0;
  }
  if (!$details['modifier']) {
    return $rate;
  }
  if ($details['method'] == 'V') {
    $rate += $details['modifier'];
  }
  else {
    $rate += ($rate * ($details['modifier']/100));
  }
  return $rate ? $rate : 0;
}


/**
 * Sets lowest room rate for a hotel_room_type node
 * @param $nid int the room nid
*/
function hotel_booking_set_room_from_rate($nid) {
  $from_rate = hotel_booking_room_from_rate($nid);
  db_query("UPDATE {hotel_booking_room_types} hbrt
           SET from_rate = %f
           WHERE nid = %d", $from_rate, $nid);
}

/**
 * Sets the lowest room rate for all rooms
 * using a rate id after a rate update
 * @param $id int the rate id
 * @param $type string the rate type hbrid|hbrmid
*/
function hotel_booking_after_price_change($id, $type) {
  $query = "SELECT nid FROM {hotel_booking_room_types}
              WHERE";
  if ($type == 'hbrid') {
    $query .= " hbrid = %d";
  }
  else {
    $query .= " hbrmid = %d";
  }
  $res = db_query($query, $id);
  while ($room = db_fetch_object($res)) {
    hotel_booking_set_room_from_rate($room->nid);
  }
}

/**
 * Make rooms auto available if set
*/
function hotel_booking_auto_availability($node = NULL) {
  $months = variable_get('hotel_booking_auto_available', 0);
  if (!$months) {
    return;
  }
  if ($node) {
    $nodes = array($node->nid);
  }
  else {
    $nodes = array();
    $res = db_query("SELECT nid FROM {node}
                    WHERE type = 'hotel_room_type'");
    while ($row = db_fetch_array($res)) {
      $nodes[] = $row['nid'];
    }
  }
  $date = date_make_date('now');
  $date->modify("+$months months");
  $sql = "SELECT MAX(calendar_dt)
          FROM {hotel_booking_availability_calendars}
          WHERE nid = %d";
  foreach ($nodes as $nid) {
    $node_date = db_result(db_query($sql, $nid));
    if ($node_date) {
      $node_date = date_make_date($node_date);
    }
    else {
      $node_date = date_make_date('now');
    }
    while ((int)$node_date->format('U') < (int)$date->format('U')) {
      $this_node = node_load($nid);
      $record = array(
        'nid' => $nid,
        'calendar_dt' => $node_date->format(DATE_FORMAT_DATE),
        'available' => $this_node->default_available
      );
      drupal_write_record('hotel_booking_availability_calendars', $record);
      $node_date->modify('+1 day');
    }
  }

}